import "babel-polyfill";
import type from "../type";

const {isFinite, isNaN} = Number;
const {PI, LN2, sign, abs, floor, exp, log, sqrt} = Math;
const {is} = Object;
const sqrt_PI = sqrt(PI);
const log_sqrt2PI = log(2 * PI) / 2;

export var toByteArray = function(x) {
    if (type(x) !== "Number") {
        throw new TypeError("Invalid Parameter Type");
    }
    var dataview = new DataView(ArrayBuffer(8));
    dataview.setFloat64(0, x);
    return [0, 1, 2, 3, 4, 5, 6, 7].map(dataview.getUint8);
};

var erf_interpolate = function(x) {
    const a = [0.254829592, -0.284496736, 1.421413741, -1.453152027, 1.061405429];
    const p = 0.3275911;
    var t = 1 / (1 + p * x);
    return ((((a[4] * t + a[3]) * t + a[2]) * t + a[1]) * t + a[0]) * t * exp(-x * x);
};

export var erf = function(x) {
    x = Number(x);
    if (isNaN(x) || !isFinite(x) || x === 0) {
        return x;
    } else if (x * x > 53 * LN2) {
        return sign(x);
    } else if (abs(x) > 1) {
        return sign(x) * (1 - erf_interpolate(abs(x)));
    } else {
        let x2 = x * x;
        let s = 1;
        let t = 1;
        for (let k = 1; ; ++k) {
            let s2 = s;
            t *= x2 / k;
            let u = t / (2 * k + 1);
            if (k & 1) {
                s -= u;
            } else {
                s += u;
            }
            if (is(s, s2)) {
                return s * 2 * x / sqrt_PI;
            }
        }
    }
};

export var erfc = function(x) {
    x = Number(x);
    if (isNaN(x)) {
        return NaN;
    } else if (x === 0) {
        return 1;
    } else if (x < 0) {
        return 2 - erf_interpolate(-x);
    } else {
        return erf_interpolate(x);
    }
};

export var gamma = function(x) {
    x = Number(x);
    if (isNaN(x)) {
        return NaN;
    } else if (x <= 0) {
        return Infinity;
    }
    const euler_gamma = 0.577215664901532860606512090;
    if (x < 0.001) {
        return 1 / (x * (1 + euler_gamma + x));
    } else if (x < 12) {
    let less_than_one = x < 1;
        let n = floor(x);
        x -= n;
        const p = [
            -1.71618513886549492533811,
            24.7656508055759199108314,
            -379.804256470945635097577,
            629.331155312818442661052,
            866.966202790413211295064,
            -31451.2729688483675254357,
            -36144.4134186911729807069,
            66456.1438202405440627855
        ];
        const q = [
            -30.8402300119738975254353,
            315.350626979604161529144,
            -1015.15636749021914166146,
            -3107.77167157231109440444,
            22538.1184209801510330112,
            4755.84627752788110767815,
            -134659.959864969306392456,
            -115132.259675553483497211
        ];
        let [num, den] = [0, 1];
        for (let i = 0; i < 8; ++i) {
            num = (num + p[i]) * x;
            den = (den * x) + q[i];
        }
        let result = num / den + 1;
        if (less_than_one) {
            result /= x;
        } else {
            for (let i = 1; i < n; ++i) {
                result *= ++x;
            }
        }
        return result;
    } else if (x > 171.624) {
        return Infinity;
    } else {
        return exp(lgamma(x));
    }
};

export var lgamma = function(x) {
    x = Number(x);
    if (isNaN(x)) {
        return NaN;
    } else if (x <= 0) {
        return Infinity;
    } else if (x < 12) {
        return log(gamma(x));
    }
    const c = [1 / 12, -1 / 360, 1 / 1260, -1 / 1680, 1 / 1188, -691 / 360360, 1 / 156, -3617 / 122400];
    var z = 1 / (x * x);
    var sum = 0;
    for (let i = 8; --i >= 0;) {
        sum = (sum * z) + c[i];
    }
    return (x - 0.5) * log(x) - x + log_sqrt2PI + sum / x;
};
